
import 'log/log_printer.dart';

Logger logger = Logger();
LoggerFactory loggerFactory = DefaultLoggerFactory();

enum LoggerLevel {
  NONE,
  ERROR,
  WARN,
  INFO,
  DEBUG,
}

class LogEvent {
  final LoggerLevel level;
  final dynamic message;
  final dynamic error;
  final StackTrace stackTrace;

  LogEvent(this.level, this.message, this.error, this.stackTrace);
}

abstract class Logger {
  factory Logger(
          {dynamic name,
          LoggerLevel? level,
          LogPrinter? printer,
          LogOutput? output,
          LogFilter? filter}) =>
      loggerFactory.getLogger(
          name: name,
          level: level,
          printer: printer,
          output: output,
          filter: filter);

  LoggerLevel get level;

  ///Log a message at the DEBUG level.
  void debug(dynamic message, [dynamic error, StackTrace? stackTrace]);

  ///Log a message at the INFO level.
  void info(dynamic message, [dynamic error, StackTrace? stackTrace]);

  ///Log a message at the WARN level.
  void warn(dynamic message, [dynamic error, StackTrace? stackTrace]);

  ///Log a message at the ERROR level.
  void error(dynamic message, [dynamic error, StackTrace? stackTrace]);

  /// Is the logger instance enabled for the DEBUG level?
  bool isDebugEnabled();

  ///Is the logger instance enabled for the ERROR level?
  bool isInfoEnabled();

  ///Is the logger instance enabled for the WARN level?
  bool isWarnEnabled();

  ///Is the logger instance enabled for the ERROR level?
  bool isErrorEnabled();
}

abstract class LoggerFactory {
  Logger getLogger(
      {dynamic name,
        LoggerLevel? level,
        LogPrinter? printer,
        LogOutput? output,
        LogFilter? filter});
}

class DefaultLoggerFactory implements LoggerFactory {
  final LogPrinter? printer;

  final LogOutput? output;

  final LogFilter? filter;

  final String? defaultName;

  final LoggerLevel loggerLevel;


  DefaultLoggerFactory(
      {this.defaultName,
        this.printer,
        this.output,
        this.filter,
        this.loggerLevel = LoggerLevel.DEBUG});

  @override
  Logger getLogger(
      {dynamic name,
      LoggerLevel? level,
      LogPrinter? printer,
      LogOutput? output,
      LogFilter? filter}) {
    return DefaultLogger(
        selfLevel: level ?? loggerLevel,
        logFilter: filter ?? this.filter,
        logPrinter: printer ?? this.printer,
        logOutput: output ?? this.output ?? ConsoleLogOutput());
  }
}

class DefaultLogger implements Logger {
  final LoggerLevel selfLevel;
  final LogFilter? logFilter;
  final LogPrinter? logPrinter;
  final LogOutput? logOutput;

  DefaultLogger(
      {this.selfLevel = LoggerLevel.DEBUG,
        this.logFilter,
        this.logPrinter,
        this.logOutput});

  @override
  LoggerLevel get level => selfLevel;

  @override
  void debug(dynamic message, [dynamic error, StackTrace? stackTrace]) {
    if (isDebugEnabled()) {
      log(LoggerLevel.DEBUG, message, error, stackTrace);
    }
  }

  @override
  void info(message, [dynamic error, StackTrace? stackTrace]) {
    if (isInfoEnabled()) {
      log(LoggerLevel.INFO, message, error, stackTrace);
    }
  }

  @override
  void error(message, [dynamic error, StackTrace? stackTrace]) {
    if (isErrorEnabled()) {
      log(LoggerLevel.ERROR, message, error, stackTrace);
    }
  }

  @override
  void warn(message, [dynamic error, StackTrace? stackTrace]) {
    if (isWarnEnabled()) {
      log(LoggerLevel.WARN, message, error, stackTrace);
    }
  }

  @override
  bool isDebugEnabled() => level.index >= LoggerLevel.DEBUG.index;

  @override
  bool isErrorEnabled() => level.index >= LoggerLevel.ERROR.index;

  @override
  bool isInfoEnabled() => level.index >= LoggerLevel.INFO.index;

  @override
  bool isWarnEnabled() => level.index >= LoggerLevel.WARN.index;


  void log(LoggerLevel level, dynamic message, [dynamic error, StackTrace? stackTrace]) {
    if (logOutput == null || logPrinter == null) {
      return;
    }
    final logEvent = LogEvent(level, message, error, stackTrace??StackTrace.current);
    if (logFilter?.shouldLog(logEvent) ?? true) {
      final logList = logPrinter!.log(logEvent);
      if (logList.isNotEmpty) {
        logOutput!.output(logList);
      }
    }
  }
}